package com.good.http;

import com.sun.jmx.snmp.ThreadContext;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

public class HttpConnectionPool {

    private volatile boolean isInit = false;

    private String poolName;

    private final static Logger logger = LoggerFactory.getLogger(HttpConnectionPool.class);

    private PoolingHttpClientConnectionManager poolConnManager;

    private RequestConfig requestConfig;
    public static final int MAX_TOTAL_POOL = 10000;
    public static final int MAX_CON_PER_ROUTE = 6000;
    private static final int SOCKET_TIMEOUT = 1000;
    public static final int CONNECTION_REQUEST_TIMEOUT = 5000;
    public static final int CONNECT_TIMEOUT = 5000;

    private int maxTotalPool = MAX_TOTAL_POOL;
    private int maxConPerRoute = MAX_CON_PER_ROUTE;
    private int socketTimeout = SOCKET_TIMEOUT;
    private int connectionRequestTimeout = CONNECTION_REQUEST_TIMEOUT;
    private int connectTimeout = CONNECT_TIMEOUT;

    public HttpConnectionPool(String poolName) {
        this.poolName = poolName;
    }


    public void init() {

        if (isInit) {
            return;
        }
        try {
            init(null, null);
        } catch (Exception e) {
            logger.error("初始化http连接池异常, poolName={}", poolName, e);
        }
    }

    private void init(String createPassword, String certPathWithFileName) {

        if (isInit) {
            return;
        }

        try {
            if (StringUtils.isEmpty(createPassword) || StringUtils.isEmpty(certPathWithFileName)) {
                initCommon(null, null);
            } else {
                FileInputStream fileInputStream = null;
                fileInputStream = new FileInputStream(new File(certPathWithFileName));// 加载本地的证书进行https加密传输
                initCommon(createPassword,fileInputStream);
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public synchronized void initCommon(String certPassword, InputStream fileInputStream) throws IOException {
        if (isInit) {
            return;
        }
        // setConnectTimeout表示设置建立连接的超时时间
        // setConnectionRequestTimeout表示从连接池中拿连接的等待超时时间
        // setSocketTimeout表示发出请求后等待对端应答的超时时间
        requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).
                setConnectionRequestTimeout(connectionRequestTimeout).
                setSocketTimeout(socketTimeout).build();
        try {
            //创建一个密钥存储容器
            RegistryBuilder<ConnectionSocketFactory> registry = RegistryBuilder.create();
            SSLContext sslContext = null;
            if (certPassword != null && null != fileInputStream) {

                //SSLContext开启TLS，并将密钥存储容器和信任任何主机的策略加载到该上下文中
                //创建证书库,初始化
                KeyStore keyStore = KeyStore.getInstance("PKCS12");
                keyStore.load(fileInputStream, certPassword.toCharArray());
                //socket 之上就变成了sslSocket ,加在http 之上就变成了 https
                sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, certPassword.toCharArray()).build();

            } else {
                // 在调用SSL之前需要重写验证方法，取消检测SSL
                X509TrustManager trustManager = new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                };
                // 初始化ssl context
                sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{trustManager}, null);
            }
            registry.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE));
            registry.register("http", PlainConnectionSocketFactory.getSocketFactory());
            poolConnManager = new PoolingHttpClientConnectionManager(registry.build());
            // 设置连接参数
            poolConnManager.setMaxTotal(maxTotalPool);//最大连接数
            poolConnManager.setDefaultMaxPerRoute(maxConPerRoute);//路由最大连接数
            SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(socketTimeout).build();
            poolConnManager.setDefaultSocketConfig(socketConfig);

            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    try {
                        // 关闭失效连接并从连接池中移除
                        poolConnManager.closeExpiredConnections();
                        // 关闭30秒钟内不活动的连接并从连接池中移除，空闲时间从交还给连接管理器时开始
                        poolConnManager.closeIdleConnections(20, TimeUnit.SECONDS);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, 0, 1000 * 5);
        } catch (Exception e) {
            logger.error("初始化http连接池异常, poolName={}", poolName, e);
        } finally {
            if (null != fileInputStream) {
                fileInputStream.close();
            }
        }
        isInit = true;
    }

    /**
     * 重试机制
     * @return
     */
    public HttpRequestRetryHandler getRetryHandler() {

        //
        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
                if (i > 3) {
                    // 重试超过3次,放弃请求
                    logger.error("retry has more than 3 time, give up request");
                    return false;

                }
                return false;
            }
        };
        return handler;
    }

    /**
     * 设置响应头信息
     */
    private void addCommonHeader(HttpRequestBase method){
        method.addHeader("Connection", "keep-alive");
        method.addHeader("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");//模拟某种浏览器，免得被特殊网关拦截
    }

    /**
     * 设置响应头信息
     * @param url
     * @return
     */
    private HttpPost getHttpPost(String url){
        HttpPost pmethod = new HttpPost(url); // 设置响应头信息

        // 设置响应头信息
        addCommonHeader(pmethod);

        return pmethod;
    }


    private HttpGet getHttpGet(String url){
        HttpGet get=new HttpGet(url);
        // 设置响应头信息
        addCommonHeader(get);

        return get;
    }

    /**
     * 添加请求头
     */
    private void addHeader(HttpRequestBase method, Map<String, String> header){
        if(null == header){
            return;
        }

        for(String key : header.keySet()){
            String value = header.get(key);

            method.addHeader(key, value);
        }
    }


    private void checkInit(){
        if(false == isInit){
            throw new RuntimeException("connectionPool还没有调用init方法");
        }
    }

    private CloseableHttpClient getConnection(){

        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(poolConnManager).setDefaultRequestConfig(requestConfig).build();
        return httpClient;
    }

    private void addContentType(HttpRequestBase method, HttpRequestContentType type, String requestCharset){
        method.addHeader("Content-Type", type.getDesc() + ";charset=" + requestCharset);
    }



    public String callByGet(String url, Map<String, String> params , Map<String, String> header, String responsecharset) throws IOException {
        checkInit();

        if(null == responsecharset){
            responsecharset = "utf-8";
        }

        // 拼接参数
        String paramStr = null;
        for (String key : params.keySet()) {
            if(null == paramStr){
                paramStr = key + "=" + params.get(key);
            }else{
                paramStr = paramStr + "&" + key + "=" + params.get(key);
            }

        }

        String queryUrl = url + "?" + paramStr;

        HttpGet httpGet = getHttpGet(queryUrl);
        addHeader(httpGet, header);
        HttpResponse response = getConnection().execute(httpGet);
        if (org.apache.commons.httpclient.HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) {
            String error = "http请求失败    url:" + url  + " statusCode=" + response.getStatusLine().getStatusCode();
            logger.error(error);

        }

        return EntityUtils.toString(response.getEntity(), responsecharset);
    }

    public String callByGet(String url,String data,String responsecharset) throws IOException {
        checkInit();

        if(null == responsecharset){
            responsecharset= "UTF-8";
        }
        String queryUrl= null;
       if(data != null){
            queryUrl = url + "?" + data;
       }else{
           queryUrl=url;
       }

        HttpGet httpGet = getHttpGet(queryUrl);

        HttpResponse httpResponse = getConnection().execute(httpGet);

        if(HttpStatus.SC_OK != httpResponse.getStatusLine().getStatusCode()){
            String error = "http请求失败    url:" + url  + " statusCode=" + httpResponse.getStatusLine().getStatusCode();
            logger.error(error);

//            throw new BusinessException(error);
        }
        String result = EntityUtils.toString(httpResponse.getEntity(), responsecharset);
        return result;
    }

    public String callByPost(String url,String data, HttpRequestContentType type, String requestCharset, String responseCharset) throws IOException {
        checkInit();

        if(null== responseCharset){
            responseCharset ="utf-8";
        }
        if(null == requestCharset){
            requestCharset = "utf-8";
        }
        if(null == type){
            type = HttpRequestContentType.JSON;
        }

        HttpPost httpPost = getHttpPost(url);


        httpPost.setEntity(new StringEntity(data, requestCharset));

        addContentType(httpPost, type, requestCharset);


        HttpResponse response = getConnection().execute(httpPost);
        if (org.apache.commons.httpclient.HttpStatus.SC_OK != response.getStatusLine().getStatusCode()) {
            String error = "http请求失败    url:" + url  + " statusCode=" + response.getStatusLine().getStatusCode();
            logger.error(error);

        }

        String result = EntityUtils.toString(response.getEntity(), responseCharset);

        return result;


    }





    public static void main(String[] args) {
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx1a1a7760d96d2568&code=001aOUL30sd6AC17PML30tRdM30aOULz&grant_type=authorization_code&secret=f984bdab1f0ede9556fb2305fe6c603a";

        HttpConnectionPool pool = HttpConnectionManager.getConnectionPool("Test");

        Map<String, String> params = new HashMap();

        String result = null;
        String queryUrl = null;

        try{
            // 拼接参数
            String paramStr = null;
            for (String key : params.keySet()) {
                if(paramStr == null){
                    paramStr= key + "=" + params.get(key);
                }else{
                    paramStr= paramStr + "&" + key + "=" + params.get(key);
                }
            }
            queryUrl = url ;
            result = pool.callByGet(queryUrl, null,"utf-8");

            System.out.println("微信openId获取结果  result:" + result);
        }catch (Exception e){
            logger.error("http请求失败    url:" + queryUrl, e);

        }

    }

}
